package com.dataneway.hybridservice.server;

import com.dataneway.hybridservice.config.NettySocketConfig;
import com.dataneway.hybridservice.entity.InitializerSetting;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PreDestroy;
import java.util.Map;

import static com.dataneway.hybridservice.ServiceConst.*;

@Component
@Slf4j
public class SocketServerStarter {

    @Autowired
    private NettySocketConfig socketConfig;

    private ServerBootstrap serverBootstrap = new ServerBootstrap();

    private EventLoopGroup boss = new NioEventLoopGroup();

    private EventLoopGroup work = new NioEventLoopGroup();

    @PreDestroy
    public void close() {
        log.info("netty server shutdown");
        boss.shutdownGracefully();
        work.shutdownGracefully();
    }

    public void start() {
        Map<Integer, InitializerSetting> socketSetting = this.socketConfig.getSocketSetting();
        try {
            serverBootstrap
                .group(boss, work)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline channelPipeline = socketChannel.pipeline();
                        channelPipeline.addLast(new LoggingHandler(LogLevel.INFO));
                        int port = socketChannel.localAddress().getPort();
                        String decoder = socketSetting.get(port).getDecoder();
                        if (!StringUtils.isEmpty(decoder)) {
                            channelPipeline.addLast(createHandlerInstance(combine(DECODER_PACKAGE, decoder)));
                        }
                        String encoder = socketSetting.get(port).getEncoder();
                        if (!StringUtils.isEmpty(encoder)) {
                            channelPipeline.addLast(createHandlerInstance(combine(ENCODER_PACKAGE, encoder)));
                        }
                        String handler = socketSetting.get(port).getHandler();
                        if (!StringUtils.isEmpty(handler)) {
                            channelPipeline.addLast(createHandlerInstance(combine(HANDLER_PACKAGE, handler)));
                        }
                    }
                });
            for (Integer port : socketSetting.keySet()) {
                log.info("netty服务器在[{}]端口启动监听", port);
                ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
                channelFuture.channel().closeFuture().addListener((ChannelFutureListener) f -> f.channel().close());
            }
        } catch (InterruptedException e) {
            log.info("出现异常，释放资源");
            e.printStackTrace();
            boss.shutdownGracefully();
            work.shutdownGracefully();
        }
    }

    private ChannelHandler createHandlerInstance(String className) throws Exception {
        try {
            Class<?> handlerClass = Class.forName(className);
            Object handlerInstance = handlerClass.newInstance();
            if (handlerInstance instanceof ChannelHandler) {
                return (ChannelHandler) handlerInstance;
            } else {
                log.error("[服务注册异常] handler类非ChannelHandler的子类，请检查配置文件: {}", className);
                throw new RuntimeException("非法类型");
            }
        } catch (ClassNotFoundException e) {
            log.error("[服务注册异常] 找不到handler类，请检查配置文件: {}", className);
            throw e;
        }
    }
}
